﻿using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using LFSRecord2.Events;
using LFSRecord2.Misc;
using LFSRecord2.Model;
using LFSRecord2.Model.UserActions;
using LFSRecord2.Structures;
using LFSRecord2.View.TimelineStructures;

namespace LFSRecord2.View.ViewportStructures
{
    /// <summary>
    /// Interaction logic for CompLayerNullViewObject.xaml
    /// </summary>
    public partial class CompLayerNullViewObject : Canvas
    {
        public static readonly DependencyProperty DoneUpdatingCurrentValuesProperty = DependencyProperty.Register(
            "DoneUpdatingCurrentValues",
            typeof(bool),
            typeof(CompLayerNullViewObject),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnDoneUpdatingCurrentValuesChanged))
        );
        public bool DoneUpdatingCurrentValues
        {
            get { return (bool)GetValue(DoneUpdatingCurrentValuesProperty); }
            set { SetValue(DoneUpdatingCurrentValuesProperty, value); }
        }
        private static void OnDoneUpdatingCurrentValuesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CompLayerNullViewObject compLayer = (CompLayerNullViewObject)d;
            CompLayerNull nullLayer = (CompLayerNull)compLayer.DataContext;
            bool mustRecreate = (nullLayer.HasParentLayer) ? true : false;

            if ((nullLayer.UpdatedCurrentValues & CompLayerPropertyUpdateTypes.Scale) > 0)
            {
                compLayer.NullScale.X = ((CompLayerPropertyPoint3D)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Scale]).CurrentXValue / 100.0d;
                compLayer.NullScale.Y = ((CompLayerPropertyPoint3D)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Scale]).CurrentYValue / 100.0d;
                compLayer.NullScale.Z = ((CompLayerPropertyPoint3D)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Scale]).CurrentZValue / 100.0d;
                mustRecreate = true;
            }

            if (mustRecreate == false && (nullLayer.UpdatedCurrentValues == 0 || compLayer.CurrentView == ViewportViews.ThreeD))
                return;

            if (nullLayer.HasParentLayer)
            {
                if (nullLayer.ParentLayer.UpdatedCurrentValues > 0)
                    compLayer.redrawKeyframes();
            }
            else if (nullLayer.UpdatedCurrentValues == CompLayerPropertyUpdateTypes.All)
            {
                compLayer.redrawKeyframes();
                compLayer.DrawSubRectangles();
            }

            //Debug.WriteLine("Updated properties (" + nullLayer.LayerName + ") : " + nullLayer.UpdatedCurrentValues);

            compLayer.redraw();
        }

        public static readonly DependencyProperty PositionPointCacheProperty = DependencyProperty.Register(
            "PositionPointCache",
            typeof(Collection<Point3DFrameCacheObject>),
            typeof(CompLayerNullViewObject),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnPositionPointCacheChanged))
        );
        public Collection<Point3DFrameCacheObject> PositionPointCache
        {
            get { return (Collection<Point3DFrameCacheObject>)GetValue(PositionPointCacheProperty); }
            set { SetValue(PositionPointCacheProperty, value); }
        }
        private static void OnPositionPointCacheChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CompLayerNullViewObject compLayer = (CompLayerNullViewObject)d;
            compLayer.DrawSubRectangles();
        }

        public static readonly DependencyProperty IsLayerSelectedProperty = DependencyProperty.Register(
            "IsLayerSelected",
            typeof(bool),
            typeof(CompLayerNullViewObject),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIsLayerSelectedChanged))
        );
        public bool IsLayerSelected
        {
            get { return (bool)GetValue(IsLayerSelectedProperty); }
            set { SetValue(IsLayerSelectedProperty, value); }
        }
        private static void OnIsLayerSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CompLayerNullViewObject compLayer = (CompLayerNullViewObject)d;
            compLayer.redrawKeyframes();
            compLayer.redraw();
        }

        public double Scale = 1;
        public Point3D NullScale = new Point3D(1, 1, 1);
        public ViewportViews CurrentView = ViewportViews.Top;

        public CompLayerNullViewObject()
        {
            InitializeComponent();

            Scale = LfsRecordController.ViewportScale;
            CurrentView = LfsRecordController.ViewportView;

            ServicesFactory.EventService.GetEvent<ViewportScaleChangedEvent>().Subscribe(ViewportScaleChangedHandler);
            ServicesFactory.EventService.GetEvent<ViewportViewChangedEvent>().Subscribe(ViewportViewChangedHandler);
        }

        public void CompLayerNullViewObject_Loaded(object sender, RoutedEventArgs e)
        {
            CompLayerNull nullLayer = (CompLayerNull)DataContext;
            nullLayer.Disposed += OnDataContextDispose;

            NullScale.X = ((CompLayerPropertyPoint3D)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Scale]).CurrentXValue / 100.0d;
            NullScale.Y = ((CompLayerPropertyPoint3D)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Scale]).CurrentYValue / 100.0d;
            NullScale.Z = ((CompLayerPropertyPoint3D)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Scale]).CurrentZValue / 100.0d;

            // Setup bindings
            Binding myBinding = new Binding("DoneUpdatingCurrentValues");
            myBinding.Source = nullLayer;
            SetBinding(CompLayerNullViewObject.DoneUpdatingCurrentValuesProperty, myBinding);

            myBinding = new Binding("IsSelected");
            myBinding.Source = nullLayer;
            SetBinding(CompLayerNullViewObject.IsLayerSelectedProperty, myBinding);

            //myBinding = new Binding("AnimationPath");
            //myBinding.Source = ((CompLayerPropertyPoint3D)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Position]);
            //PositionPath.SetBinding(Path.DataProperty, myBinding);

            myBinding = new Binding("Point3DFrameCache");
            myBinding.Source = (CompLayerPropertyPoint3D)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Position];
            SetBinding(CompLayerNullViewObject.PositionPointCacheProperty, myBinding);

            //createProjectedMeshPoints();
            redraw();
        }

        private void OnDataContextDispose(object sender, EventArgs e)
        {
            ServicesFactory.EventService.GetEvent<ViewportScaleChangedEvent>().Unsubscribe(ViewportScaleChangedHandler);
            ServicesFactory.EventService.GetEvent<ViewportViewChangedEvent>().Unsubscribe(ViewportViewChangedHandler);
        }

        private void KeyFramesControl_Loaded(object sender, RoutedEventArgs e)
        {
            CompLayerNull nullLayer = (CompLayerNull)DataContext;

            KeyFramesControl.ItemsSource = nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Position].KeyFrames;
        }

        private void ViewportScaleChangedHandler(double scale)
        {
            Scale = scale;

            CompLayerNull nullLayer = (CompLayerNull)DataContext;
            if (nullLayer.Properties.Count == 0)
            {
                Debug.WriteLine("Euhm, wanted to draw a null layer with no properties?");
                return;
            } 
            
            //createProjectedMeshPoints();
            redraw();

            updateKeyFrameDrawingScales();
            DrawSubRectangles();
        }

        public void ViewportViewChangedHandler(ViewportViewChangedEventArgs e)
        {
            CurrentView = e.View;

            if (CurrentView != ViewportViews.ThreeD)
            {
                //Debug.WriteLine("Viewport view changed : " + e.View);
                //createProjectedMeshPoints();
                redraw();
                DrawSubRectangles();
            }

            Collection<CompLayerKeyFrameDrawing> list;
            CompLayerNull nullLayer = (CompLayerNull)DataContext;
            for (int a = 0; a < nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Position].KeyFrames.Count; a++)
            {
                list = VisualHelper.FindVisualChildren<CompLayerKeyFrameDrawing>(KeyFramesControl.ItemContainerGenerator.ContainerFromIndex(a));

                foreach (CompLayerKeyFrameDrawing kfd in list)
                {
                    kfd.ViewportViewChangedHandler(CurrentView);
                }
            }
        }

        private void updateKeyFrameDrawingScales()
        {
            Collection<CompLayerKeyFrameDrawing> list;
            CompLayerNull nullLayer = (CompLayerNull)DataContext;
            for (int a = 0; a < nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Position].KeyFrames.Count; a++)
            {
                list = VisualHelper.FindVisualChildren<CompLayerKeyFrameDrawing>(KeyFramesControl.ItemContainerGenerator.ContainerFromIndex(a));

                foreach (CompLayerKeyFrameDrawing kfd in list)
                {
                    kfd.ViewportScaleChangedHandler(Scale);
                }
            }

            PositionPath.StrokeThickness = 0.5 / Scale;
        }

        private void redrawKeyframes()
        {
            Collection<CompLayerKeyFrameDrawing> list;
            CompLayerNull nullLayer = (CompLayerNull)DataContext;
            if (!nullLayer.IsSelected)
                return;

            for (int a = 0; a < nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Position].KeyFrames.Count; a++)
            {
                list = VisualHelper.FindVisualChildren<CompLayerKeyFrameDrawing>(KeyFramesControl.ItemContainerGenerator.ContainerFromIndex(a));

                foreach (CompLayerKeyFrameDrawing kfd in list)
                {
                    kfd.redraw();
                }
            }

            PositionPath.StrokeThickness = 0.5 / Scale;
        }

        private void redraw()
        {
            CompLayerNull nullLayer = (CompLayerNull)DataContext;

            // Retrieve a transformed layer object (mesh)
            CompLayerMeshBase layerObjectMesh = nullLayer.CreateTransformedMesh(LfsRecordController.FrameIndex, new Vector3D(1 / Scale, 1 / Scale, 1 / Scale));

            // Draw the new MainObject geometry
            GeometryGroup tlrGroup = new GeometryGroup();
            LineGeometry lineGeom;
            for (int a = 0; a < layerObjectMesh.NumLineIndexes; a++)
            {
                lineGeom = new LineGeometry(
                    new Point(layerObjectMesh.Points[layerObjectMesh.LineIndexes[a]].X, layerObjectMesh.Points[layerObjectMesh.LineIndexes[a]].Y),
                    new Point(layerObjectMesh.Points[layerObjectMesh.LineIndexes[a + 1]].X, layerObjectMesh.Points[layerObjectMesh.LineIndexes[a + 1]].Y));
                lineGeom.Freeze();
                tlrGroup.Children.Add(lineGeom);

                a++;
            }

            // Add it to the drawer object
            GeometryDrawing gd = new GeometryDrawing();
            gd.Geometry = tlrGroup;
            gd.Pen = new Pen(Brushes.Red, ((IsLayerSelected) ? 2 / Scale : 1 / Scale));
            //MainObjectBounds = gd.Bounds;

            // Apply it to the MainObject Image
            MainObject.Source = new DrawingImage(gd);
            MainObject.Stretch = Stretch.None;

            MainObject.RenderTransform = new TranslateTransform(
                gd.Bounds.Left,
                gd.Bounds.Top);

            // Position crosshair
            TransformGroup trGroup = new TransformGroup();
            trGroup.Children.Add(new ScaleTransform(1 / Scale, 1 / Scale));
            trGroup.Children.Add(new TranslateTransform(layerObjectMesh.CenterPoint.X, layerObjectMesh.CenterPoint.Y));
            PositionCrossHair.RenderTransform = trGroup;

            if (nullLayer.HasParentLayer)
            {
                DrawSubRectangles();
            }
        }

        private void DrawSubRectangles()
        {
            //Debug.WriteLine("redrawing sub frame rectangles - " + PositionPointCache.Count);
            CompLayerNull nullLayer = (CompLayerNull)DataContext;

            if (PositionPointCache == null || PositionPointCache.Count == 0)
            {
                PositionSubFrameRectangles.Source = null;
                PositionPath.Data = null;
                return;
            }

            Matrix3D m = Matrix3D.Multiply(nullLayer.GetParentedTransformMatrixAtFrame(LfsRecordController.FrameIndex, false), LfsRecordController.ViewTransformMatrix);
            Point3D p;

            GeometryGroup lineGeomGroup = new GeometryGroup();
            StreamGeometry lineGeometry = new StreamGeometry();
            StreamGeometryContext ctxLine = lineGeometry.Open();

            GeometryGroup rectGeomGroup = new GeometryGroup();
            StreamGeometry rectGeometry;
            rectGeomGroup.FillRule = FillRule.Nonzero;

            double rectRadius = 0.75 * 1 / Scale;
            for (int a = 0; a < PositionPointCache.Count; a++)
            {
                p = m.Transform(PositionPointCache[a].Value);

                if (nullLayer.IsSelected)
                {
                    rectGeometry = new StreamGeometry();
                    rectGeometry.FillRule = FillRule.EvenOdd;
                    using (StreamGeometryContext ctxRect = rectGeometry.Open())
                    {
                        ctxRect.BeginFigure(new Point(p.X - rectRadius, p.Y), false, false);
                        ctxRect.LineTo(new Point(p.X + rectRadius, p.Y), true, false);
                    }
                    rectGeometry.Freeze();
                    rectGeomGroup.Children.Add(rectGeometry);
                }

                // line
                if (a == 0)
                {
                    ctxLine.BeginFigure(new Point(p.X, p.Y), false, false);
                }
                else
                {
                    ctxLine.LineTo(new Point(p.X, p.Y), true, false);
                }
            }
            ctxLine.Close();

            if (nullLayer.IsSelected)
            {
                GeometryDrawing gd = new GeometryDrawing();
                gd.Geometry = rectGeomGroup;
                gd.Brush = Brushes.Beige;
                gd.Pen = new Pen(Brushes.White, rectRadius * 2);
                gd.Freeze();

                PositionSubFrameRectangles.Source = new DrawingImage(gd);
                PositionSubFrameRectangles.RenderTransform = new TranslateTransform(gd.Bounds.Left, gd.Bounds.Top);
            }

            // Now populate the path
            lineGeometry.Freeze();
            lineGeomGroup.Children.Add(lineGeometry);

            PositionPath.Data = lineGeometry;
        }

        Point _prevPoint;
        bool _objectHasMoved;
        Vector3D _objectMoveVec;
        bool _hasUnderlyingKeyFrame = false;
        private void Crosshair_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            e.Handled = true;
            CompLayerNull nullLayer = (CompLayerNull)DataContext;
            CompLayerPropertyPoint3D prop = (CompLayerPropertyPoint3D)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Position];

            if (prop.HasBoundProperty)
                return;

            if (prop.IsAnimated)
            {
                // Use underlying keyframe?
                _hasUnderlyingKeyFrame = true;
                CustomPoint3DKeyFrame kfDrag = (CustomPoint3DKeyFrame)prop.GetKeyAtTime(LfsRecordController.FrameIndex);
                if (kfDrag == null)
                {
                    _hasUnderlyingKeyFrame = false;

                    if (!nullLayer.IsSelected)
                    {
                        LfsRecordController.project.DeselectAll();
                        nullLayer.IsSelected = true;
                    }
                }
                else
                {
                    if (!kfDrag.IsSelected && !Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
                        LfsRecordController.project.DeselectAll();
                    kfDrag.IsSelected = true;
                }
            }
            else
            {
                _hasUnderlyingKeyFrame = false;
                LfsRecordController.project.DeselectAll();
                nullLayer.IsSelected = true;
            }

            // Start mouse move capturing
            Application.Current.MainWindow.MouseMove += Crosshair_MouseMove;
            Application.Current.MainWindow.MouseUp += Crosshair_MouseUp;
            _prevPoint = e.GetPosition(null);

            this.IsHitTestVisible = false;
            _objectHasMoved = false;
            _objectMoveVec = new Vector3D();
        }

        private void Crosshair_MouseUp(object sender, MouseButtonEventArgs e)
        {
            CompLayerNull nullLayer = (CompLayerNull)DataContext;
            CompLayerPropertyPoint3D prop = (CompLayerPropertyPoint3D)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Position];

            e.Handled = true;
            this.IsHitTestVisible = true;

            Application.Current.MainWindow.MouseMove -= Crosshair_MouseMove;
            Application.Current.MainWindow.MouseUp -= Crosshair_MouseUp;

            if (_objectHasMoved)
            {
                if (_hasUnderlyingKeyFrame)
                    LfsRecordController.project.SubmitUserActionSelectedKeyFramePoint3DValueChange(new Vector3D(-_objectMoveVec.X, -_objectMoveVec.Y, -_objectMoveVec.Z));
                else
                    LfsRecordController.RecordUserAction(new UserActionKeyFramePoint3DValueChanged()
                    {
                        LayerIndex = prop.ParentLayer.LayerIndex,
                        PropertyIndex = prop.PropertyIndex,
                        KeyFrameIndex = -1,
                        ValueOffset = new Vector3D(-_objectMoveVec.X, -_objectMoveVec.Y, -_objectMoveVec.Z)
                    });
                LfsRecordController.CreateUndoLevel();
            }

            _objectHasMoved = false;
        }

        private void Crosshair_MouseMove(object sender, MouseEventArgs e)
        {
            Point mPos = e.GetPosition(null);
            Vector3D diff3D = new Vector3D(_prevPoint.X - mPos.X, _prevPoint.Y - mPos.Y, 0);
            _prevPoint = mPos;

            if (diff3D.X == 0 && diff3D.Y == 0)
                return;
            _objectHasMoved = true;

            CompLayerNull nullLayer = (CompLayerNull)DataContext;

            Matrix3D invMat = Matrix3D.Multiply(nullLayer.GetParentedTransformMatrixAtFrame(LfsRecordController.FrameIndex, false), LfsRecordController.ViewTransformMatrix);
            invMat.OffsetX = 0;
            invMat.OffsetY = 0;
            invMat.OffsetZ = 0;
            invMat.Invert();
            diff3D = invMat.Transform(diff3D);

            _objectMoveVec += diff3D;

            CompLayerPropertyPoint3D prop = (CompLayerPropertyPoint3D)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Position];
            if (!_hasUnderlyingKeyFrame && prop.IsAnimated)
            {
                // Set new keyframe
                CustomPoint3DKeyFrame kfDrag = (CustomPoint3DKeyFrame)prop.SetKeyAtCurrentTime(LfsRecordController.FrameIndex);
                if (kfDrag == null)
                    return;

                LfsRecordController.project.DeselectAll();
                kfDrag.IsSelected = true;
                _hasUnderlyingKeyFrame = true;

                // Record user action of added keyframe
                LfsRecordController.RecordUserAction(new UserActionAddKeyframe()
                {
                    LayerIndex = kfDrag.ParentLayer.LayerIndex,
                    PropertyIndex = kfDrag.ParentProperty.PropertyIndex,
                    KeyFrame = kfDrag.DataStructure()
                });
            }

            //Debug.WriteLine("Moving by : {0}, {1}", diff.X, diff.Y);
            if (prop.IsAnimated)
            {
                LfsRecordController.project.TranslateSelectedKeyFrames(diff3D.X / Scale, diff3D.Y / Scale, diff3D.Z / Scale);
            }
            else
            {
                prop.CurrentXValue -= diff3D.X / Scale;
                prop.CurrentYValue -= diff3D.Y / Scale;
                prop.CurrentZValue -= diff3D.Z / Scale;
                prop.OnCurrentValueChange();
            }

            //ServicesFactory.EventService.GetEvent<CompLayerPropertyChangedEvent>().Publish(new CompLayerPropertyChangedEventArgs() { Sender = this, PropertyType = "Value", UpdateCurrentValues = false });
            
            if (!_hasUnderlyingKeyFrame)
            {
                nullLayer.SetCurrentTransformMatrix();
            }

            nullLayer.UpdatedCurrentValues = CompLayerPropertyUpdateTypes.Position;
            nullLayer.DoneUpdatingCurrentValues = !nullLayer.DoneUpdatingCurrentValues;
            //redraw();
        }

        private void Crosshair_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            CompLayerNull nullLayer = (CompLayerNull)DataContext;
            CompLayerPropertyPoint3D prop = (CompLayerPropertyPoint3D)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Position];
            if (prop.HasBoundProperty)
                return;

            // Use underlying keyframe
            CustomPoint3DKeyFrame kf = (CustomPoint3DKeyFrame)prop.GetKeyAtTime(LfsRecordController.FrameIndex);
            if (kf != null)
            {
                e.Handled = true;
                KeyImageControl ki = new KeyImageControl();
                ki.DataContext = kf;
                ki.SetupContextMenu();
                ki.ContextMenu.IsOpen = true;
            }
        }
    }
}
